package com.mtiiot.fstcpserver.fastdfs.core;

import java.io.IOException;
import java.util.Arrays;

public class Base64
{
  private String lineSeparator = System.getProperty("line.separator");

  private int lineLength = 72;

  private char[] valueToChar = new char[64];

  private int[] charToValue = new int[256];

  private int[] charToPad = new int[4];
  static final int IGNORE = -1;
  static final int PAD = -2;
  private static final boolean debug = true;

  public Base64()
  {
    init('+', '/', '=');
  }

  public Base64(char chPlus, char chSplash, char chPad, int lineLength)
  {
    init(chPlus, chSplash, chPad);
    this.lineLength = lineLength;
  }

  public Base64(int lineLength)
  {
    this.lineLength = lineLength;
  }

  private void init(char chPlus, char chSplash, char chPad)
  {
    int index = 0;

    for (int i = 65; i <= 90; i++) {
      this.valueToChar[(index++)] = ((char)i);
    }

    for (int i = 97; i <= 122; i++) {
      this.valueToChar[(index++)] = ((char)i);
    }

    for (int i = 48; i <= 57; i++) {
      this.valueToChar[(index++)] = ((char)i);
    }

    this.valueToChar[(index++)] = chPlus;
    this.valueToChar[(index++)] = chSplash;

    for (int i = 0; i < 256; i++)
    {
      this.charToValue[i] = -1;
    }

    for (int i = 0; i < 64; i++)
    {
      this.charToValue[this.valueToChar[i]] = i;
    }

    this.charToValue[chPad] = -2;
    Arrays.fill(this.charToPad, chPad);
  }

  public String encode(byte[] b)
    throws IOException
  {
    int outputLength = (b.length + 2) / 3 * 4;

    if (this.lineLength != 0)
    {
      int lines = (outputLength + this.lineLength - 1) / this.lineLength - 1;
      if (lines > 0)
      {
        outputLength += lines * this.lineSeparator.length();
      }

    }

    StringBuffer sb = new StringBuffer(outputLength);

    int linePos = 0;

    int len = b.length / 3 * 3;
    int leftover = b.length - len;
    for (int i = 0; i < len; i += 3)
    {
      linePos += 4;
      if (linePos > this.lineLength)
      {
        if (this.lineLength != 0)
        {
          sb.append(this.lineSeparator);
        }
        linePos = 4;
      }

      int combined = b[(i + 0)] & 0xFF;
      combined <<= 8;
      combined |= b[(i + 1)] & 0xFF;
      combined <<= 8;
      combined |= b[(i + 2)] & 0xFF;

      int c3 = combined & 0x3F;
      combined >>>= 6;
      int c2 = combined & 0x3F;
      combined >>>= 6;
      int c1 = combined & 0x3F;
      combined >>>= 6;
      int c0 = combined & 0x3F;

      sb.append(this.valueToChar[c0]);
      sb.append(this.valueToChar[c1]);
      sb.append(this.valueToChar[c2]);
      sb.append(this.valueToChar[c3]);
    }

    switch (leftover)
    {
    case 0:
    default:
      break;
    case 1:
      linePos += 4;
      if (linePos > this.lineLength)
      {
        if (this.lineLength != 0)
        {
          sb.append(this.lineSeparator);
        }
        linePos = 4;
      }

      sb.append(encode(new byte[] { b[len], 0, 0 }).substring(0, 2));

      sb.append("==");
      break;
    case 2:
      linePos += 4;
      if (linePos > this.lineLength)
      {
        if (this.lineLength != 0)
        {
          sb.append(this.lineSeparator);
        }
        linePos = 4;
      }

      sb.append(encode(new byte[] { b[len], b[(len + 1)], 0 }).substring(0, 3));

      sb.append("=");
    }

    if (outputLength != sb.length())
    {
      System.out.println("oops: minor program flaw: output length mis-estimated");
      System.out.println("estimate:" + outputLength);
      System.out.println("actual:" + sb.length());
    }
    return sb.toString();
  }

  public byte[] decodeAuto(String s)
  {
    int nRemain = s.length() % 4;
    if (nRemain == 0) {
      return decode(s);
    }
    return decode(s + new String(this.charToPad, 0, 4 - nRemain));
  }

  public byte[] decode(String s)
  {
    byte[] b = new byte[s.length() / 4 * 3];

    int cycle = 0;

    int combined = 0;

    int j = 0;

    int len = s.length();
    int dummies = 0;
    for (int i = 0; i < len; i++)
    {
      int c = s.charAt(i);
      int value = c <= 255 ? this.charToValue[c] : -1;

      switch (value)
      {
      case -1:
        break;
      case -2:
        value = 0;
        dummies++;
      default:
        switch (cycle)
        {
        case 0:
          combined = value;
          cycle = 1;
          break;
        case 1:
          combined <<= 6;
          combined |= value;
          cycle = 2;
          break;
        case 2:
          combined <<= 6;
          combined |= value;
          cycle = 3;
          break;
        case 3:
          combined <<= 6;
          combined |= value;

          b[(j + 2)] = ((byte)combined);
          combined >>>= 8;
          b[(j + 1)] = ((byte)combined);
          combined >>>= 8;
          b[j] = ((byte)combined);
          j += 3;
          cycle = 0;
        }
        break;
      }
    }

    if (cycle != 0)
    {
      throw new ArrayIndexOutOfBoundsException("Input to decode not an even multiple of 4 characters; pad with =.");
    }
    j -= dummies;
    if (b.length != j)
    {
      byte[] b2 = new byte[j];
      System.arraycopy(b, 0, b2, 0, j);
      b = b2;
    }
    return b;
  }

  public void setLineLength(int length)
  {
    this.lineLength = (length / 4 * 4);
  }

  public void setLineSeparator(String lineSeparator)
  {
    this.lineSeparator = lineSeparator;
  }

  public static void show(byte[] b)
  {
    int count = 0;
    int rows = 0;

    for (int i = 0; i < b.length; i++)
    {
      if (count == 8)
      {
        System.out.print("  ");
      }
      else if (count == 16)
      {
        System.out.println("");
        count = 0;
        continue;
      }
      System.out.print(Integer.toHexString(b[i] & 0xFF).toUpperCase() + " ");
      count++;
    }

    System.out.println();
  }

  public static void display(byte[] b)
  {
    for (int i = 0; i < b.length; i++)
    {
      System.out.print((char)b[i]);
    }
    System.out.println();
  }

  public static void main(String[] args)
  {
    test();
    System.exit(1);
    try
    {
      Base64 b64 = new Base64();
      String str = "agfrtu¿¦etÊ²1234¼Ù´óerty¿Õ234·¢¿¦2344Ê²µÄ";
      String str64 = "";

      str64 = b64.encode(str.getBytes());
      System.out.println(str64);

      byte[] theBytes = b64.decode(str64);
      show(theBytes);
      String rst = new String(theBytes);
      System.out.println(rst);
      System.out.println(str);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public static void test()
  {
    try
    {
      Base64 b64 = new Base64();

      String str64 = "CwUEFYoAAAADjQMC7ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267EI=";

      byte[] theBytes = b64.decode(str64);
      show(theBytes);
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}